package com.xht.security.oauth2.server.resource.introspection;

import cn.hutool.extra.spring.SpringUtil;
import com.xht.constant.SecurityConstant;
import com.xht.security.core.userdetails.XhtUser;
import com.xht.security.core.userdetails.XhtUserDetailsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;

import java.security.Principal;
import java.util.Comparator;
import java.util.Map;
import java.util.Objects;

/**
 * <h1>描述 ：处理不透明令牌的方法</h1>
 * 不透明令牌是指在令牌中不包含任何实际信息，而是包含一个指向的令牌服务器中包含真实信息的令牌的标识符。
 * 在OAuth2中，Bearer令牌通常就是不透明令牌。OpaqueTokenIntrospector的实现类可以通过向令牌服务器发送请求获取令牌的真实信息，并
 * 将其作为认证信息保存在Spring Security的SecurityContext中，以便后续的鉴权操作。
 *
 * @author : 小糊涂
 * @version : 1.0
 * @see OpaqueTokenIntrospector
 **/
@Slf4j
@RequiredArgsConstructor
public class CustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector {

    private final OAuth2AuthorizationService authorizationService;

    /**
     * Introspect and verify the given token, returning its attributes.
     * <p>
     * Returning a {@link java.util.Map} is indicative that the token is valid.
     *
     * @param token the token to introspect
     * @return the token's attributes
     */
    @Override
    public OAuth2AuthenticatedPrincipal introspect(String token) {
        OAuth2Authorization oldAuthorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
        if (Objects.isNull(oldAuthorization)) {
            throw new InvalidBearerTokenException(token);
        }
        // 客户端模式默认返回
        if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(oldAuthorization.getAuthorizationGrantType())) {
            return new DefaultOAuth2AuthenticatedPrincipal(oldAuthorization.getPrincipalName(), oldAuthorization.getAttributes(), AuthorityUtils.NO_AUTHORITIES);
        }
        Map<String, XhtUserDetailsService> userDetailsServiceMap = SpringUtil.getBeansOfType(XhtUserDetailsService.class);
        XhtUserDetailsService userDetailsService = userDetailsServiceMap.values()
                .stream()
                .filter(service -> service.support(Objects.requireNonNull(oldAuthorization).getRegisteredClientId(),
                        oldAuthorization.getAuthorizationGrantType().getValue()))
                .max(Comparator.comparingInt(Ordered::getOrder)).orElseThrow(() -> {
                    throw new OAuth2AuthenticationException("未找到符合的 org.springframework.security.core.userdetails.UserDetailsService");
                });
        UserDetails userDetails = null;
        try {
            Object principal = Objects.requireNonNull(oldAuthorization).getAttributes().get(Principal.class.getName());
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) principal;
            Object tokenPrincipal = usernamePasswordAuthenticationToken.getPrincipal();
            userDetails = userDetailsService.loadUserByUser((XhtUser) tokenPrincipal);
        } catch (UsernameNotFoundException notFoundException) {
            log.warn("用户不不存在 {}", notFoundException.getLocalizedMessage());
            throw notFoundException;
        } catch (Exception ex) {
            log.error("资源服务器 introspect Token error {}", ex.getLocalizedMessage());
        }
        // 注入扩展属性,方便上下文获取客户端ID
        XhtUser xhtUser = (XhtUser) userDetails;
        Objects.requireNonNull(xhtUser).getAttributes().put(SecurityConstant.CLIENT_ID, oldAuthorization.getRegisteredClientId());
        return xhtUser;
    }
}
